home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
CD ROM Paradise Collection 4
/
CD ROM Paradise Collection 4 1995 Nov.iso
/
science
/
cdrift3.zip
/
TEC1.C
< prev
next >
Wrap
C/C++ Source or Header
|
1990-02-22
|
22KB
|
470 lines
/* This program is Copyright (c) 1990 David Allen. It may be freely
distributed as long as you leave my name and copyright notice on it.
I'd really like your comments and feedback; send e-mail to
davea@vlsi.ll.mit.edu, or send us-mail to David Allen, 10 O'Moore Ave,
Maynard, MA 01754. */
/* This is the file containing all of the important functions except for
trysplit (), which splits a continent into pieces. Also, all of the main
arrays are declared here, even a couple that are only used by functions in
tec2.c. The array declarations are first, followed by the sequencing
function onestep () and some miscellaneous routines including the text
output routines; initialization routines and the routines that do all
the interesting stuff are last. */
#include "const.h"
#include "var.h"
#include <stdio.h>
/* These defines are used for the PostScript output section of tecst(). */
#define D ((double) 432 / ((XSIZE > YSIZE) ? XSIZE : YSIZE))
#define ZMAX 128
#define XX(x) (108 + ((x) * D))
#define YY(y) (612 - ((y) * D))
#define ZZ(z) (1 - (float) ((z > ZMAX) ? ZMAX : z) / (ZMAX))
/* The following arrays are global and most are used by functions in both
source files. The two main ones are m and t. Each is set up to be two
2-d arrays, where each array is the size of the whole world. M is the
map; elements in m are indices of plates, showing which squares are
covered by which plate. T is the topography; elements in t are altitudes. */
char m[2][MAXX][MAXY]; unsigned char t[2][MAXX][MAXY];
/* Several arrays are used by the binary blob segmenter, segment() in tec2.c.
These include r, which is used to store fragment indices; many fragments
make up one region during a segmentation. Kid is a lookup table; fragment
k belongs to region kid[k] after a segmentation is finished. Karea[k]
is the area of fragment k. */
char r[MAXX][MAXY], kid[MAXFRAG]; short karea [MAXFRAG];
/* The merge routine gets information from the move routine; when the move
routine puts a square of one plate on top of another plate, that information
is recorded in the merge matrix mm. */
char mm[MAXPLATE][MAXPLATE];
/* The erosion routine needs an array to store delta information; during an
erosion, the increases or decreases in elevation are summed in e and then
applied all at once to the topography. */
char e[MAXX][MAXY];
/* Several routines need temporary storage for areas and plate identifiers. */
short tarea[MAXPLATE]; short ids[MAXPLATE];
/* The plates in use are stored in this data structure. Dx,dy are the
values to move by THIS STEP ONLY; odx,ody are the permanent move
values; rx,ry are the remainder x and y values used by newdxy() to
determine dx,dy; age is the age of the plate, in steps; area is the
area of the plate, in squares; id is the value in the m array which
corresponds to this plate; next is a pointer to the next occupied
element of the plate array. */
struct plate p [MAXPLATE];
/* The linked list header for available plates and used plates are global,
as is the step counter. */
short pavail, phead, step;
onestep () {
/* This is the sequencing routine called by main once per step.
It just calls the important subfunctions in order:
- trysplit finds a plate to break up, and computes new velocities
- newdxy computes the deltas to move each plate this step
- move moves the plates
- merge determines results when plates rub together
- erode erodes the terrain, adding or subtracting at the margins
- draw draw the resulting array once every DRAWEVERY steps
The m and t arrays are double-buffered in the sense that operations go
from m[0] to m[1] or vice-versa; src and dest determine which is which. */
short src, dest;
src = step % 2; dest = 1 - src;
if (rnd (100) < RIFTPCT) trysplit (src);
newdxy ();
move (src, dest);
merge (dest);
if (DOERODE) erode (dest);
if (step && !(step % DRAWEVERY)) draw (dest); }
palloc () {
/* Allocate a plate from the array and return its index. All the fields
of the plate are initialized to 0, except `next'. That field is used to
link together the plate structures in use. */
short x;
if (!pavail) panic ("No more objects");
x = pavail; pavail = p[x].next;
p[x].next = phead; phead = x;
p[x].area = 0; p[x].age = 0;
p[x].rx = 0; p[x].ry = 0;
p[x].odx = 0; p[x].ody = 0;
p[x].dx = 0; p[x].dy = 0;
return ((int) x); }
pfree (n) short n; {
/* Return a plate array element to the pool of available elements.
To check for infinite loops, the variable guard is incremented
at each operation; if the number of operations exceeds the maximum
possible number, the program panics. */
short i, guard = 0;
if (phead == n) phead = p[n].next;
else {
for (i=phead; p[i].next!=n; i=p[i].next)
if (++guard > MAXPLATE) panic ("Infinite loop in pfree");
p[i].next = p[n].next; }
p[n].next = pavail; pavail = n; }
tecst (src, drawmode) short src, drawmode; {
/* This function is called whenever map output is called for. It looks
at the parameter `drawmode' to decide between long text, simple text,
and PostScript output formats. Note that the default for this
function is no output at all, corresponding to DRAWMODE_NONE. */
register short i,j,k;
if (drawmode == DRAWMODE_GENERIC)
for (j=0; j<YSIZE; j++) for (i=0, k=0; i<XSIZE; i++) {
printf ("%4d", t[src][i][j]);
if (!(++k % 18)) printf ("\n"); }
else if (drawmode == DRAWMODE_TEXT) {
for (j=0; j<YSIZE; j++) {
for (i=0; i<XSIZE; i++) {
if ((k = t[src][i][j]) < ZCOAST) k = 0;
else if (k > ZMOUNTAIN) k = 2; else k = 1;
printf ("%d", k); }
printf ("\n"); }
printf ("\n"); }
else if (drawmode == DRAWMODE_GRAY) {
printf ("%%!PS-Adobe-1.0\n/d %4.2f def\n", D);
printf ("37.5 45 { dup mul exch dup mul add 1 exch sub } setscreen\n");
printf ("/r { setgray moveto d 0 rlineto 0 d rlineto ");
printf ("d neg 0 rlineto fill } bind def\n");
printf ("%6.2f %6.2f moveto\n", XX(-1), YY(-1));
printf ("%6.2f %6.2f lineto\n", XX(XSIZE), YY(-1));
printf ("%6.2f %6.2f lineto\n", XX(XSIZE), YY(YSIZE));
printf ("%6.2f %6.2f lineto\n", XX(-1), YY(YSIZE));
printf ("%6.2f %6.2f lineto\nstroke\n", XX(-1), YY(-1));
for (j=0; j<YSIZE;j++) for (i=0; i<XSIZE; i++)
if ((k = t[src][i][j]) > ZCOAST)
printf ("%6.2f %6.2f %5.4f r\n", XX(i), YY(j), ZZ(k));
printf ("\nshowpage\n"); } }
init (s) char *s; {
/* This is the catchall function that initializes everything. First,
it calls getparams() in tec3.c to allow the user to set parameters. Next,
it links together the plates onto the free list and starts the used list
at empty. The first plate is created by a fractal technique and then
improved. Finally, the fractal is copied to the data array and drawn.
There are two kinds of improvement done here. First, islands are
eliminated by segmenting the blob and erasing all the regions except
for the biggest. Second, oceans inside the blob (holes) are eliminated
by segmenting the _ocean_ and filling in all regions except the biggest. */
short besti, x; register short i, j;
if (s) if (*s) getparams (s);
for (i=1; i<MAXPLATE; i++) p[i].next = i + 1;
p[MAXPLATE-1].next = 0;
pavail = 1; phead = 0;
/* Allocate a plate structure for the first plate and make a blob */
x = palloc (); makefrac (0, x);
/* Segment m[0] looking for x, set besti to the largest region, */
/* and zero out all the other regions. This eliminates islands. */
besti = singlefy (0, x);
if (besti > 0) for (i=1; i<XSIZE; i++) for (j=1; j<YSIZE; j++)
if (kid[r[i][j]] != besti) m[0][i][j] = 0;
/* Segment m[0] looking for 0 (ocean), set besti to the largest region, */
/* and fill in all the other regions. This eliminates holes in the blob. */
besti = singlefy (0, 0);
if (besti > 0) for (i=1; i<XSIZE; i++) for (j=1; j<YSIZE; j++)
if (kid[r[i][j]] != besti) m[0][i][j] = x;
/* Fill the topo structure with the blob shape while finding its area */
for (i=0; i<XSIZE; i++) for (j=0; j<YSIZE; j++)
if (m[0][i][j]) { t[0][i][j] = ZINIT; p[x].area++; }
/* Draw the blob */
draw (0); }
makefrac (src, match) short src, match; {
/* This function uses a very simple fractal technique to draw a blob. Four
one-dimensional fractals are created and stored in array x, then the
fractals are superimposed on a square array, summed and thresholded to
produce a binary blob. Squares in the blob are set to the value in `match'.
A one-dimensional fractal of length n is computed like this. First,
set x [n/2] to some height and set the endpoints (x[0] and x[n]) to 0.
Then do log-n iterations. The first iteration computes 2 more values:
x[n/4] = average of x[0] and x[n/2], plus some random number, and
x[3n/4] = average of x[n/2] and x[n], plus some random number. The second
iteration computes 4 more values (x[n/8], x[3n/8], ...) and so on. The
random number gets smaller by a factor of two each time also.
Anyway, you wind up with a number sequence that looks like the cross-section
of a mountain. If you sum two fractals, one horizontal and one vertical,
you get a 3-d mountain; but it looks too symmetric. If you sum four,
including the two 45 degree diagonals, you get much better results. */
register short xy, dist, n, inc, i, j, k; char x[4][65];
short xofs, yofs, xmin, ymin, xmax, ymax;
/* Compute offsets to put center of blob in center of the world, and
compute array limits to clip blob to world size */
xofs = (XSIZE - 64) >> 1; yofs = (YSIZE - 64) >> 1;
if (xofs < 0) { xmin = -xofs; xmax = 64 + xofs; }
else { xmin = 0; xmax = 64; }
if (yofs < 0) { ymin = -yofs; ymax = 64 + yofs; }
else { ymin = 0; ymax = 64; }
for (xy=0; xy<4; xy++) {
/* Initialize loop values and fractal endpoints */
x [xy] [0] = 0; x [xy] [64] = 0; dist = 32;
x [xy] [32] = 24 + rnd (16); n = 2; inc = 16;
/* Loop log-n times, each time halving distance and doubling iterations */
for (i=0; i<5; i++, dist>>=1, n<<=1, inc>>=1)
for (j=0, k=0; j<n; j++, k+=dist)
x[xy][k+inc] = ((x[xy][k]+x[xy][k+dist])>>1) + rnd (dist) - inc; }
/* Superimpose fractals; if sum is greater than BLOBLEVEL, there will be */
/* land there. x[0] is horizontal, x[1] vertical, x[2] diagonal from */
/* top left to bottom right, x[3] diagonal from TR to BL. */
for (i=xmin; i<xmax; i++) for (j=ymin; j<ymax; j++) {
k = x[0][i] + x[1][j] + x[2][(i - j + 64) >> 1] + x[3][(i + j) >> 1];
if (k > BLOBLEVEL) m[src][i+xofs][j+yofs] = match; } }
singlefy (src, match) short src, match; {
/* This is a subfunction of init() which is called twice to improve the
fractal blob. It calls segment() and then interprets the result. If
only one region was found, no improvement is needed; otherwise, the
area of each region must be computed by summing the areas of all its
fragments, and the index of the largest region is returned. */
short i, reg, frag, besti, besta;
segment (src, match, &frag, ®);
if (reg == 1) return (-1); /* No improvement needed */
/* Initialize the areas to zero, then sum frag areas */
for (i=1; i<=reg; i++) tarea[i] = 0;
for (i=1; i<=frag; i++) tarea [kid[i]] += karea [i];
/* Pick largest area of all regions and return it */
for (i=1, besta=0, besti=0; i<=reg; i++)
if (besta < tarea[i]) { besti = i; besta = tarea[i]; }
return ((int) besti); }
newdxy () {
/* For each plate, compute how many squares it should move this step.
Multiply the plate's basic movement vector odx,ody by the age modifier
MR[], then add the remainders rx,ry from the last move to get some large
integers. Then turn the large integers into small ones by dividing by
REALSCALE and putting the remainders back into rx,ry. This function also
increases the age of each plate, but doesn't let the age of any plate
go above MAXLIFE. This is done to make sure that MR[] does not need to
be a long vector. */
register short i, a;
for (i=phead; i; i=p[i].next) {
a = (p[i].odx * MR[p[i].age]) + p[i].rx;
p[i].dx = a / REALSCALE; p[i].rx = a % REALSCALE;
a = (p[i].ody * MR[p[i].age]) + p[i].ry;
p[i].dy = a / REALSCALE; p[i].ry = a % REALSCALE;
if (p[i].age < MAXLIFE-1) (p[i].age)++; } }
move (src, dest) short src, dest; {
/* This function moves all the plates that are drifting. The amount to
move by is determined in newdxy(). The function simply steps through
every square in the array; if there's a plate in a square, its new location
is found and the topography is moved there. Overlaps between plates are
detected and recorded so that merge() can resolve the collision; mountain
growing is performed. If two land squares wind up on top of each other,
folded mountains are produced. If a land square winds up where ocean was
previously, that square is the leading edge of a continent and grows a
mountain by subsuming the ocean basin. */
register short i, j; short a, b, c, x, y;
/* Clear out the merge matrix and the destination arrays */
for (i=1; i<MAXPLATE; i++) for (j=1; j<MAXPLATE; j++) mm[i][j] = 0;
for (i=0; i<XSIZE; i++) for (j=0; j<YSIZE; j++) {
m[dest][i][j] = 0; t[dest][i][j] = 0; }
/* Look at every square which belongs to a plate */
for (i=0; i<XSIZE; i++) for (j=0; j<YSIZE; j++) if ((a = m[src][i][j]) > 0) {
/* Add the plate's dx,dy to the position to get the square's new */
/* location; if it is off the map, throw it away */
x = p[a].dx + i; y = p[a].dy + j;
if ((x >= XSIZE) || (x < 0) || (y >= YSIZE) || (y < 0)) p[a].area--;
else { /* It IS on the map */
/* If the destination is occupied, remove the other guy but */
/* remember that the two plates overlapped; set the new height */
/* to the larger height plus half the smaller. */
if (c = m[dest][x][y]) {
(mm[a][c])++; (p[c].area)--;
b = t[src][i][j]; c = t[dest][x][y];
t[dest][x][y] = (b > c) ? b + (c>>1) : c + (b>>1); }
/* The destination isn't occupied. Just copy the height. */
else t[dest][x][y] = t[src][i][j];
/* If this square is over ocean, increase its height. */
if (t[src][x][y] < ZCOAST) t[dest][x][y] += ZSUBSUME;
/* Plate A now owns this square */
m[dest][x][y] = a; } } }
merge (dest) short dest; {
/* Since move has set up the merge matrix, most of the work is done. This
function calls bump once for each pair of plates which are rubbing; note
that a and b below loop through the lower diagonal part of the matrix.
One subtle feature is that a plate can bump with several other plates in
a step; suppose that the plate is merged with the first plate it bumped.
The loop will try to bump the vanished plate with the other plates, which
would be wrong. To avoid this, the lookup table lut is used to provide
a level of indirection. When a plate is merged with another, its lut
entry is changed to indicate that future merges with the vanished plate
should be applied to the plate it has just been merged with. */
char lut[MAXPLATE]; short a, aa, b, bb, i;
for (a=1; a<MAXPLATE; a++) lut[a] = a;
for (a=2; a<MAXPLATE; a++) for (b=1; b<a; b++) if (mm[a][b] || mm[b][a]) {
aa = lut [a]; bb = lut[b];
if (aa != bb) if (bump (dest, aa, bb)) {
lut[aa] = bb;
for (i=1; i<MAXPLATE; i++) if (lut[i] == aa) lut[i] = bb; } } }
bump (dest, a, b) short dest, a, b; {
/* Plates a and b have been moved on top of each other by some amount;
alter their movement rates for a slow collision, possibly merging them.
The collision "strength" is a ratio of the area overlap (provided by
move ()) to the total area of the plates involved. A fraction of each
plate's current movement vector is subtracted from the movement vector
of the other plate. If the two vectors are now within some tolerance
of each other, they are almost at rest so merge them with each other. */
double maa, mab, ta, tb, rat, area; register short i, j, x;
/* Find a ratio describing how strong the collision is */
x = mm[a][b] + mm[b][a]; area = p[a].area + p[b].area;
rat = x / (MAXBUMP + (area / 20)); if (rat > 1.0) rat = 1.0;
/* Do some math to update the move vectors. This looks complicated */
/* because a plate's actual movement vector must be multiplied by */
/* MR[age], and because I have rewritten the equations to maximize */
/* use of common factors. Trust me, it's just inelastic collision. */
maa = p[a].area * MR[p[a].age]; mab = p[b].area * MR[p[b].age];
ta = MR[p[a].age] * area;
p[a].odx = (p[a].odx * maa + p[b].odx * mab * rat) / ta;
p[a].ody = (p[a].ody * maa + p[b].ody * mab * rat) / ta;
tb = MR[p[b].age] * area;
p[b].odx = (p[b].odx * mab + p[a].odx * maa * rat) / tb;
p[b].ody = (p[b].ody * mab + p[a].ody * maa * rat) / tb;
/* For each axis, compute the remaining relative velocity. If it is */
/* too large, return without merging the plates */
if (ABS (p[a].odx*MR[p[a].age] - p[b].odx*MR[p[b].age]) > BUMPTOL) return(0);
if (ABS (p[a].ody*MR[p[a].age] - p[b].ody*MR[p[b].age]) > BUMPTOL) return(0);
/* The relative velocity is small enough, so merge the plates. Replace */
/* all references to a with b, free a, and tell merge() a was freed. */
for (i=0; i<XSIZE; i++) for (j=0; j<YSIZE; j++)
if (m[dest][i][j] == a) m[dest][i][j] = b;
p[b].area += p[a].area; pfree (a);
return ((int) a); }
/* The following is defined as a macro for efficiency; erosion is still
the slowest function in the simulation. ERODE is called four times per
pixel by erode. A and b are altitudes of two adjacent squares, while c
and d are the corresponding delta values for those squares. The amount
each square loses to an adjacent but lower square in each step is
one-eighth the difference in altitude. This is coded as a shift right 3
bits, but since -1 >> 3 is still -1, the code must be repeated to avoid
negative deltas. */
#define ERODE(a,b,c,d) \
if (((a > ZSHELF) || (b > ZSHELF))) { \
if ( a > b) { x = (a - b + ERODERND) >> 3; c -= x; d += x; } \
else { x = (b - a + ERODERND) >> 3; c += x; d -= x; } }
erode (dest) short dest; {
/* This function takes the topography in t[dest] and smooths it, lowering
mountains and raising lowlands and continental margins. It does this by
stepping across the entire array and doing a computation once for each
pair of 8-connected pixels. The computation is done by ERODE, above.
The computation result for a pair is a small delta for each square, which
is summed in the array e. When the computation is finished, the delta
is applied; if this pushes an ocean square high enough, it is added to
an adjacent plate if one can be found. Also, if a land square is eroded
low enough, it is turned into ocean and removed from its plate. */
register short i, j, t1, x, z; short ii, jj, xx;
/* Zero out the array for the deltas first */
for (i=0; i<XSIZE; i++) for (j=0; j<YSIZE; j++) e[i][j] = 0;
/* Step across the entire array; each pixel is adjacent to 8 others, and */
/* it turns out that if four pairs are considered for each pixel, each */
/* pair is considered exactly once. This is important for even erosion */
for (i=1; i<XSIZE; i++) for (j=1; j<YSIZE; j++) {
t1 = t[dest][i][j];
ERODE (t1, t[dest][i][j-1], e[i][j], e[i][j-1])
ERODE (t1, t[dest][i-1][j-1], e[i][j], e[i-1][j-1])
ERODE (t1, t[dest][i-1][j], e[i][j], e[i-1][j])
if (j < YSIZE-1) {
ERODE (t1, t[dest][i-1][j+1], e[i][j], e[i-1][j+1]) } }
/* Now go back across the array, applying the delta values from e[][] */
for (i=0; i<XSIZE; i++) for (j=0; j<YSIZE; j++) {
z = t[dest][i][j] + e[i][j]; if (z < 0) z = 0; if (z > 255) z = 255;
/* If the square just rose above shelf level, look at the four */
/* adjacent squares. If one is a plate, add the square to that plate */
if ((z >= ZSHELF) && (t[dest][i][j] < ZSHELF)) {
if (i > 1) if (xx = m[dest][i-1][j]) { ii = i-1; jj = j; }
if (i < XSIZE-1) if (xx = m[dest][i-1][j]) { ii = i+1; jj = j; }
if (j > 1) if (xx = m[dest][i][j-1]) { ii = i; jj = j-1; }
if (j < YSIZE-1) if (xx = m[dest][i][j+1]) { ii = i; jj = j+1; }
p[xx].area++; m[dest][i][j] = xx; }
/* If the square is lower than shelf level but belongs to a plate, */
/* remove it from the plate */
if (((t[dest][i][j] = z) < ZSHELF) && (x = m[dest][i][j])) {
p[x].area--; m[dest][i][j] = 0; } } }